package net.aiion.weave.impl.cards.xml;

import net.aiion.weave.asm.Opcodes;
import net.aiion.weave.impl.cards.events.CardClassEndEventImpl;
import net.aiion.weave.impl.cards.events.CardClassStartEventImpl;
import net.aiion.weave.impl.cards.events.CardProcessCardMethodEndEventImpl;
import net.aiion.weave.impl.cards.events.CardProcessCardMethodStartEventImpl;
import net.aiion.weave.spi.bytecode.BytecodeGenerator;
import net.aiion.weave.spi.cards.Card;
import net.aiion.weave.spi.cards.CardContext;
import net.aiion.weave.spi.cards.CardXmlNames;
import net.aiion.weave.spi.naming.annotations.Named;
import net.aiion.weave.spi.naming.annotations.UserScoped;
import net.aiion.weave.spi.xml.XmlContext;
import net.aiion.weave.spi.xml.XmlSink;
import net.aiion.weave.spi.xml.events.DocumentEndXmlEvent;
import net.aiion.weave.spi.xml.events.DocumentStartXmlEvent;
import net.aiion.weave.spi.xml.events.ElementEndXmlEvent;
import net.aiion.weave.spi.xml.events.ElementStartXmlEvent;
import net.aiion.weave.spi.xml.events.XmlEvent;

@UserScoped
@Named
public class ContainerTransformation implements XmlSink
{
    private static final String PROP_INTERNAL_NAME = "internalName";

    private boolean inCard;

    private BytecodeGenerator bytecodeGenerator;

    public ContainerTransformation() {
    }

    @Override
    public void handleXmlEvent(final XmlContext aXmlContext) {
        bytecodeGenerator = aXmlContext.getProperty("bytecodeGenerator", BytecodeGenerator.class);
        final XmlEvent xmlEvent = aXmlContext.getXmlEvent();
        switch (xmlEvent.getType()) {
        case XmlEvent.START_DOCUMENT:
            handleDocumentStartXmlEvent(aXmlContext, xmlEvent.asDocumentStart());
            break;
        case XmlEvent.START_ELEMENT:
            handleElementStartXmlEvent(aXmlContext, xmlEvent.asElementStart());
            break;
        case XmlEvent.END_ELEMENT:
            handleElementEndXmlEvent(aXmlContext, xmlEvent.asElementEnd());
            break;
        case XmlEvent.END_DOCUMENT:
            handleDocumentEndXmlEvent(aXmlContext, xmlEvent.asDocumentEnd());
            break;
        default:
            handleXmlEvent(aXmlContext, xmlEvent);
            break;
        }
    }

    private void handleDocumentStartXmlEvent(final XmlContext aXmlContext,
        final DocumentStartXmlEvent aDocumentStartXmlEvent)
    {
        bytecodeGenerator.startClass(Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER,
            aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class), Object.class, Card.class);
        aXmlContext.forwardXmlEvent(new CardClassStartEventImpl());
    }

    private void handleElementStartXmlEvent(final XmlContext aXmlContext,
        final ElementStartXmlEvent aElementStartXmlEvent)
    {
        if (CardXmlNames.CONTAINER.equals(aElementStartXmlEvent.getName())) {
            inCard = true;

            bytecodeGenerator.startMethod(Opcodes.ACC_PUBLIC, "processCard", void.class, CardContext.class);
            bytecodeGenerator.startCode();
            aXmlContext.forwardXmlEvent(new CardProcessCardMethodStartEventImpl());
            return;
        }

        handleXmlEvent(aXmlContext);
    }

    private void handleElementEndXmlEvent(final XmlContext aXmlContext, final ElementEndXmlEvent aElementEndXmlEvent) {
        if (CardXmlNames.CONTAINER.equals(aElementEndXmlEvent.getName())) {
            inCard = false;

            aXmlContext.forwardXmlEvent(new CardProcessCardMethodEndEventImpl());
            bytecodeGenerator.returnVoid();
            // TODO calculate maxStack and maxLocals and insert them here
            bytecodeGenerator.endCode(0, 0);
            bytecodeGenerator.endMethod();
            return;
        }

        handleXmlEvent(aXmlContext);
    }

    private void
        handleDocumentEndXmlEvent(final XmlContext aXmlContext, final DocumentEndXmlEvent aDocumentEndXmlEvent)
    {
        aXmlContext.forwardXmlEvent(new CardClassEndEventImpl());
        bytecodeGenerator.endClass();
    }

    private void handleXmlEvent(final XmlContext aXmlContext, final XmlEvent aXmlEvent) {
        if (inCard) {
            aXmlContext.forwardXmlEvent(aXmlEvent);
        }
    }
}
